package han.cirno.cirno_dao

import com.google.devtools.ksp.processing.Dependencies
import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.processing.SymbolProcessor
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
import com.google.devtools.ksp.processing.SymbolProcessorProvider
import com.google.devtools.ksp.symbol.KSAnnotated
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSVisitorVoid
import com.google.devtools.ksp.validate
import java.io.PrintWriter

/**
 * 第一次写注解解析器，有点紧张（）
 * 希望不要写出史吧。。。
 * @author CCirno
 */
internal class ProcessorProvider :SymbolProcessorProvider{
    override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor = Processor(environment)
}

internal class Processor(private val environment: SymbolProcessorEnvironment) :SymbolProcessor{
    override fun process(resolver: Resolver): List<KSAnnotated> {
        val symbols =
            resolver.getSymbolsWithAnnotation(CirnoDaoAwA::class.qualifiedName!!)
        val notValid=ArrayList<KSAnnotated>()
        symbols.forEach {
            if (!it.validate()){
                notValid.add(it)
                return@forEach
            }
            it.accept(Visitor(environment),Unit)
        }
        return notValid
    }
}

internal class Visitor(private val environment: SymbolProcessorEnvironment):KSVisitorVoid(){
    private val randoms= arrayOf(
        "スターいっぱい夢いっぱいです！",
        "100% Bad Code!",
        //"Funding for this program was made possible by viewers like you!"
    )

    override fun visitClassDeclaration(classDeclaration: KSClassDeclaration, data: Unit) {
        val packageName = classDeclaration.containingFile!!.packageName.asString()
        val originClassName = classDeclaration.simpleName.asString()
        val fullClassName=classDeclaration.qualifiedName!!.asString()
        val className = "${originClassName}Dao"
        val newFileOutput = environment.codeGenerator.createNewFile(
            Dependencies(
                true,
                classDeclaration.containingFile!!
            ), packageName, className
        )
        val allProperties = classDeclaration.getAllProperties()
        val propertyNames=allProperties.map{
            return@map Pair(it.simpleName.asString(),it.type.resolve().declaration.qualifiedName!!.asString())
        }
        val linkNameMap=allProperties.flatMap {
            val linkNames=(it.annotations.find {a-> a.shortName.asString()==CirnoLinkQuery::class.simpleName!! }
                ?:return@flatMap emptySequence()).arguments[0].value!! as ArrayList<*>
            return@flatMap linkNames.asSequence().map {n-> Pair(n!! as String,it.simpleName.asString()) }
        }.groupBy({it.first},{it.second})
        val singleQueryMap=allProperties.mapNotNull {
            if(it.annotations.any { a->a.shortName.asString()==CirnoSingleQuery::class.simpleName!! })
                return@mapNotNull Pair(it.simpleName.asString(),it.type.resolve().declaration.qualifiedName!!.asString())
            return@mapNotNull null
        }
        val singleDeleteMap=allProperties.mapNotNull {
            if(it.annotations.any { a->a.shortName.asString()==CirnoSingleDelete::class.simpleName!! })
                return@mapNotNull Pair(it.simpleName.asString(),it.type.resolve().declaration.qualifiedName!!.asString())
            return@mapNotNull null
        }
        val extraFunctions=classDeclaration.annotations.flatMap {
            if(it.shortName.asString()==CirnoDaoExtra::class.simpleName!!)
                return@flatMap (it.arguments[0].value as ArrayList<*>).asSequence()
            return@flatMap emptySequence()
        }
        val colQueryMap=allProperties.mapNotNull {
            if(it.annotations.any { a->a.shortName.asString()==CirnoColQuery::class.simpleName!!})
                return@mapNotNull Pair(it.simpleName.asString(),it.type.resolve().declaration.qualifiedName!!.asString())
            return@mapNotNull null
        }
        val shadowMethods=ArrayList<ShadowMethod>().apply {
            singleQueryMap.forEach { p ->
                add(
                    "@Query(\"SELECT * FROM $originClassName WHERE ${p.first} = :${p.first}\")",
                    "getCursorBy${p.first.cap()}",
                    "${p.first}:${p.second}",
                    "Cursor")
                add(
                    "@Query(\"SELECT * FROM $originClassName WHERE ${p.first} = :${p.first}\")",
                    "getListBy${p.first.cap()}",
                    "${p.first}:${p.second}",
                    "List<$fullClassName>")
            }
            singleDeleteMap.forEach { p ->
                add(
                    "@Delete",
                    "getDeleteBy${p.first.cap()}",
                    "${p.first}:${p.second}")
            }
            linkNameMap.forEach {
                val key = it.key
                val queryString =
                    "SELECT * FROM $originClassName WHERE " + it.value.joinToString(" and ") { s -> return@joinToString "$s = :$s" }
                val propertyPairs = propertyNames.mapNotNull { n ->
                    if (it.value.contains(n.first)) return@mapNotNull "${n.first}:${n.second}"
                    return@mapNotNull null
                }.joinToString(",")
                add(
                    "@Query(\"$queryString\")",
                    "getCursorBy$key",
                    propertyPairs,
                    "Cursor")
                add(
                    "@Query(\"$queryString\")",
                    "getListBy${key}",
                    propertyPairs,
                    "List<$fullClassName>")
            }
            colQueryMap.forEach {
                add("@Query(\"SELECT ${it.first} FROM $originClassName\")","getCol${it.first.cap()}","","List<${it.second}>")
            }
            add("@Query(\"SELECT * FROM $originClassName LIMIT 1\")","getFirst","", fullClassName)
            add("@Query(\"SELECT * FROM $originClassName\")","getAll","","Cursor")
            add("@Query(\"SELECT * FROM $originClassName\")","getAllList","","List<$fullClassName>")
            add("@Query(\"SELECT * FROM $originClassName\")","getAllListFlow","","Flow<List<$fullClassName>>")
            add("@Query(\"SELECT * FROM $originClassName ORDER BY uid LIMIT 1\")","getFirstUidOrder","", fullClassName)
            add("@Query(\"SELECT * FROM $originClassName ORDER BY uid\")","getAllUidOrder","","Cursor")
            add("@Query(\"SELECT * FROM $originClassName ORDER BY uid\")","getAllListUidOrder","","List<$fullClassName>")
            add("@Update","update", "${originClassName.low()}:$fullClassName")
            add("@Update","updateAll","${originClassName.low()}List:List<$fullClassName>","Int")
            add("@Insert(onConflict = OnConflictStrategy.REPLACE)","insert","arg:${fullClassName}")
            add("@Insert(onConflict = OnConflictStrategy.REPLACE)","insertAll","vararg args:${fullClassName}")
            add("@Insert(onConflict = OnConflictStrategy.REPLACE)","insertAll","list:List<${fullClassName}>")
            add("@Delete","delete","arg:${fullClassName}")
            add("@Query(\"DELETE FROM $originClassName\")","deleteAll","")
        }
        PrintWriter(newFileOutput).use {
            val codes= arrayOf(
                "/**",
                " * This file is generated by CirnoDao OvO",
                " * Here\'s the details:",
                " * Properties:",
                *asArray {a-> propertyNames.forEach { a.add(" *   ${it.first} - ${it.second}") } },
                " * Single queries:",
                *asArray { a-> singleQueryMap.forEach { a.add(" *   ${it.first} - ${it.second}") } },
                " * Link queries:",
                *asArray { a->linkNameMap.forEach { a.add(" *   ${it.key} -> ${it.value.joinToString(", ")}") } },
                " * Extras:",
                *asArray { a->extraFunctions.forEach { a.add(" *   $it") } },
                " * @author CCirno(\'s generator)",
                " *",
                " * \"${randoms.random()}\"",
                " */",
                "package $packageName",
                "",
                "import javax.annotation.processing.Generated",
                "import android.database.Cursor",
                "import androidx.room.Insert",
                "import androidx.room.Delete",
                "import androidx.room.Query",
                "import androidx.room.Update",
                "import androidx.room.OnConflictStrategy",
                "import androidx.room.Dao",
                "import androidx.room.TypeConverters",
                "import kotlinx.coroutines.Dispatchers",
                "import kotlinx.coroutines.runBlocking",
                "import kotlinx.coroutines.flow.Flow",
                "",
                "@Generated(\"han.cirno.cirno_dao.Processor\")",
                "@Dao",
                "interface $className{",
                *asArray {a->
                    shadowMethods.forEach {
                        a.add("    ${it.annotation} fun _${it.name}(${it.parameter}):${it.returnType}")
                    }
                },
                "",
                *asArray {a->
                    shadowMethods.forEach {
                        a.add("    fun ${it.name}(${it.parameter}):${it.returnType} = runBlocking(Dispatchers.Default){ _${it.name}(${
                            it.parameter.split(",").joinToString(",") {s-> 
                                val name=s.split(":")[0]
                                if(name.contains("vararg"))"*${name.split(" ")[1]}"
                                else name
                            }})}")
                    }
                },
                "}")
            codes.forEach { c-> it.println(c) }
        }
        newFileOutput.close()
    }

    private fun String.cap():String= this.replaceFirstChar { it.uppercaseChar() }

    private fun String.low():String = this.replaceFirstChar { it.lowercaseChar() }

    private fun ArrayList<ShadowMethod>.add(annotation: String,name: String,parameter: String,returnType: String="Unit"){
        this.add(ShadowMethod(annotation, name, parameter, returnType))
    }

    private fun asArray(r:(MutableList<String>)->Unit):Array<String>{
        val list=ArrayList<String>()
        r(list)
        return list.toTypedArray()
    }
}

internal data class ShadowMethod(
    val annotation:String,
    val name:String,
    val parameter:String,
    val returnType:String)

@Retention(AnnotationRetention.SOURCE)
@Target(AnnotationTarget.CLASS)
annotation class CirnoDaoAwA

@Retention(AnnotationRetention.SOURCE)
@Target(AnnotationTarget.FIELD)
annotation class CirnoSingleQuery

@Suppress("unused")
@Retention(AnnotationRetention.SOURCE)
@Target(AnnotationTarget.FIELD)
annotation class CirnoLinkQuery(val links:Array<String> =["default"])

@Retention(AnnotationRetention.SOURCE)
@Target(AnnotationTarget.FIELD)
annotation class CirnoColQuery

@Retention(AnnotationRetention.SOURCE)
@Target(AnnotationTarget.FIELD)
annotation class CirnoSingleDelete

@Retention(AnnotationRetention.SOURCE)
@Target(AnnotationTarget.CLASS)
annotation class CirnoDaoExtra(val classes:Array<String> =[])
